Skip to content

Add PR monitoring: server-driven babysit mode for agent threads - #4428

Open
t3dotgg wants to merge 7 commits into
mainfrom
t3code/add-pr-monitoring-flow
Open

Add PR monitoring: server-driven babysit mode for agent threads#4428
t3dotgg wants to merge 7 commits into
mainfrom
t3code/add-pr-monitoring-flow

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Jul 24, 2026

Copy link
Copy Markdown
Member

Threads can now formally monitor a PR after creating it. The agent calls a new monitor_start MCP tool; a dedicated PullRequestMonitor service polls GitHub every 30s, diffs review comments / reviews / check runs against a per-thread cursor, and wakes the thread with a self-contained prompt when there is something to address. The server — never the agent — declares the PR ready from head-SHA-anchored GitHub state.

What's included

  • GitHub monitor snapshot (gitHubPullRequestMonitor.ts): reviews, review threads + resolution, check runs, head SHA, mergeability; pure cursor-diff and readiness modules with false-green guards (stale approvals don't count, zero checks ≠ green, changes-requested blocks until re-review)
  • Domain events thread.monitor-started/-snapshot-updated/-ended through decider/projector; monitor state on the thread shell; migration 035 (monitor_json)
  • Wake dispatch protocol: claim → fresh terminal revalidation at the send boundary → dispatch → cursor ack only after accepted start. User turns always win and rewind in-flight wake cursors so events re-diff instead of being lost. 10-wake circuit breaker ends as needs-attention
  • Lifecycle: monitoring holds the thread unsettled; user settle ends the monitor; session teardown ends it as session-ended; merged/closed PRs end it as terminal with the whole poll cycle discarded (no post-merge resurrection)
  • UI: steady cyan Monitoring pill (never masks approval/input/failed), blockers sub-line, header MonitorStrip with Stop, emerald Ready-to-merge state behind a confirm dialog. Clients can only end a monitor with reason "stopped" — the other reasons are server verdicts
  • Rebased over thread snoozing (feat(sidebar-v2): thread snoozing #4311); snooze + monitor coexist on the lifecycle files

Design docs: UX plan https://lmrxu2kpvpm0.postplan.dev · implementation plan https://nr9llb5mku4z.postplan.dev

Testing

  • New unit tests: cursor diff, readiness, dispatch-protocol (terminal-discards, ack ordering, failed-dispatch-preserves-cursor, busy veto, generation change, breaker), decider/projector monitor events, threadSettled monitor blocker, sidebar pill priority
  • Full suites green post-rebase: apps/server 1640, apps/web 1513, client-runtime 473

🤖 Generated with Claude Code


Note

High Risk
Large new orchestration + in-process polling path that auto-starts at boot, dispatches agent turns from GitHub state, and uses module-global monitor registrations that are lost on restart until boot reconcile.

Overview
Agents can start PR babysit mode via a new MCP monitor_start tool (with monitor capability on MCP sessions). The server registers the thread in a shared MonitorRegistry, emits thread.monitor.* commands/events, and runs PullRequestMonitor on a ~30s poll loop that fetches GitHub PR state, diffs review/CI changes, evaluates merge readiness, and dispatches thread.turn.start wake prompts when needed—with claim/ack/rewind logic for races, user turns, and provider failures, plus a 10-wake needs-attention stop.

Orchestration & persistence: New monitor lifecycle in the decider/projector (start clears settled override; settle/archive end monitoring), monitor_json on projected threads (migration 035), and session teardown that ends monitors before revoking MCP.

UI: MonitorStrip in chat (stop / ready / merge confirm) and sidebar status pills (Monitoring, Ready to merge, Needs attention) that do not override approval/input/failed states.

Reviewed by Cursor Bugbot for commit d1faaa1. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add server-driven PR monitoring with agent babysit mode for orchestration threads

  • Introduces a full PR monitoring pipeline: a PullRequestMonitor actor starts at server boot, polls GitHub for PR state changes (reviews, check runs, behind-base, thread updates), diffs against a cursor, and wakes the agent thread with a structured prompt when actionable events are detected.
  • Adds MonitorRegistry to track per-thread monitor state (generation, cursor, wake count) module-globally so both the runtime core and MCP routes share the same instance.
  • Extends the orchestration contracts with thread.monitor.start/update/end commands and corresponding events; the decider and projector handle the full monitor lifecycle and persist state in a new monitor_json column (migration 035).
  • Exposes a new MCP tool monitor_start that agents can call to begin monitoring a PR; MCP credentials now include the monitor capability by default.
  • Adds MonitorStrip to the chat header and updates sidebar rows with monitoring-aware pills (Monitoring, Needs attention, Ready to merge) and blockers summary; threads with an active monitor are always considered unsettled.
  • Risk: the monitor actor starts automatically as part of reactor initialization and polls GitHub CLI on an interval — environments without GitHub CLI access or valid credentials will see monitor start failures at boot.

Macroscope summarized d1faaa1.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c625b82c-8650-4902-9e98-6215d8881819

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/add-pr-monitoring-flow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Jul 24, 2026
Comment thread apps/web/src/components/SidebarV2.tsx
Comment thread apps/server/src/monitor/PullRequestMonitor.ts Outdated
Comment thread apps/server/src/sourceControl/gitHubPullRequestMonitor.ts
Comment thread apps/server/src/monitor/PullRequestMonitor.ts
Comment thread apps/server/src/monitor/wakePrompt.ts Outdated
Comment thread apps/server/src/mcp/toolkits/monitor/handlers.ts Outdated
Comment thread apps/server/src/monitor/monitorDiff.ts
Comment thread apps/server/src/monitor/readiness.ts Outdated
Comment thread apps/server/src/sourceControl/gitHubPullRequestMonitor.ts
Comment thread apps/server/src/monitor/MonitorRegistry.ts
Comment thread apps/server/src/monitor/monitorDiff.ts
Comment thread apps/server/src/provider/Layers/ProviderService.ts
Comment thread apps/server/src/monitor/readiness.ts
Comment thread apps/server/src/monitor/PullRequestMonitor.ts Outdated
Comment thread apps/server/src/monitor/PullRequestMonitor.ts
Comment thread apps/server/src/monitor/MonitorRegistry.ts
Comment thread apps/server/src/monitor/readiness.ts
Comment thread apps/server/src/monitor/PullRequestMonitor.ts
@macroscopeapp

macroscopeapp Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a substantial new feature (PR monitoring/babysit mode) with new server-side services, MCP toolkit, orchestration events, database migration, and UI components spanning ~3000+ lines of new code. New features of this scope warrant human review to validate the design and runtime behavior.

You can customize Macroscope's approvability policy. Learn more.

Comment thread apps/server/src/sourceControl/gitHubPullRequestMonitor.ts
Comment thread apps/server/src/monitor/PullRequestMonitor.ts Outdated
Comment thread apps/server/src/monitor/PullRequestMonitor.ts Outdated
Comment thread apps/server/src/monitor/PullRequestMonitor.ts
Comment thread apps/server/src/monitor/PullRequestMonitor.ts Outdated
t3dotgg and others added 5 commits July 26, 2026 19:39
Threads can now formally monitor a PR after creating it. The agent calls a
new monitor_start MCP tool; a dedicated PullRequestMonitor service polls
GitHub every 30s, diffs review comments / reviews / check runs against a
per-thread cursor, and wakes the thread with a self-contained prompt when
there is something to address. The server — never the agent — declares the
PR ready from head-SHA-anchored GitHub state.

- GitHub monitor snapshot fetch (reviews, threads, check runs, mergeability)
  with pure cursor-diff and readiness modules
- thread.monitor.start/update/end domain events through decider/projector,
  monitor state on the thread shell, migration 034 (monitor_json)
- Wake dispatch protocol: claim → fresh terminal revalidation at the send
  boundary → dispatch → cursor ack only after accepted start; user turns
  always win and rewind in-flight wake cursors so events re-diff; 10-wake
  circuit breaker ends as needs-attention
- Monitoring holds the thread unsettled; user settle ends the monitor;
  session teardown ends it as session-ended
- UI: steady cyan Monitoring pill (never masks approval/input/failed),
  blockers sub-line, header MonitorStrip with Stop, emerald Ready-to-merge
  state with confirm dialog; clients can only end a monitor as "stopped"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Stop prepareMcpSession ending the active monitor on session start
  (session recycles no longer kill their own babysit)
- Fail closed at the wake send boundary: a failed fresh PR fetch discards
  the wake instead of falling back to the stale poll state
- Correlate provider.turn.start.failed releases by dispatch time and
  rewind wakeCount alongside the cursor
- Monotonic generation allocator; generation-conditional
  updateCursor/incrementWake/remove so stale polls can't mutate a
  newer monitor
- Dispatch thread.monitor.end before removing the registration in
  session teardown; end monitors on thread.archived
- Preserve wakeCount on idempotent monitor.start replay (breaker can't
  be reset)
- Key check-run cursors by head SHA + name + run id; detect
  resolved→unresolved thread reopens
- Human changes-requested reviews now block readiness
- Thread monitoringStartedAt from the registration so pre-existing
  unresolved threads don't block readiness forever
- Use the PR's base ref in the wake prompt instead of hard-coded main
- Boot reconcile: threads projecting "monitoring" with no registration
  are ended as session-ended

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Ignore review-thread activity whose latest comment is the viewer's own
  reply, breaking the self-triggered wake cycle
- Retry boot reconciliation on the first successful poll instead of
  swallowing a failed shell snapshot at startup
- Remove the registration when archive teardown dispatch fails so
  archived threads are never polled or woken again
- Stamp wake claims with the turn command's createdAt so provider
  turn-start failures (which echo that timestamp) match the release
  condition; a fresh post-dispatch timestamp could never match

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from a multi-agent review panel (23 confirmed after two-lens
adversarial verification), grouped by root cause:

Lifecycle identity
- Thread a monitor generation through monitor commands, events, and the
  projected monitor state; reject updates/ends whose generation is not
  the active one, and make every registry removal generation-conditional
  so a delayed lifecycle event cannot delete a newer monitor
- Re-validate registration, projected monitor status, archive state,
  generation, and claim at the wake send boundary (after the fresh fetch,
  not before it)

Event loss
- Diff before the readiness exit: actionable activity now wins over
  ending as ready, so a bot comment landing in the same poll as green CI
  is never discarded
- Persist non-actionable cursor transitions so resolved→reopened is
  still detectable
- Acknowledge the cursor only if the wake claim still exists, so a
  user-turn release is not overwritten

Readiness
- Collapse reviews to each reviewer's latest opinionated review, so an
  approval supersedes that reviewer's earlier changes-requested
- Re-include pre-monitor review threads once they are edited or reopened

Snapshot integrity
- Final terminal/head revalidation after pagination, with one bounded
  refetch on head change
- Fetch the last 10 comments per review thread and track per-thread
  updatedAt so edits to non-final comments are visible
- Reopened threads wake regardless of self-authorship
- Include the changes-requested review body in the wake prompt

Lifecycle events
- Archive atomically emits thread.monitor-ended before thread.archived,
  mirroring settle
- Treat a queued-but-unadopted user turn as busy at the send boundary
- Boot reconciliation isolates per-thread failures (Bugbot)

UI
- Gate ready-state PR actions on the checkout PR matching the monitored
  PR number
- Wake snoozed threads on monitor ready/needs-attention/session-ended
- Legacy sidebar renders the ready payoff and a needs-attention state;
  session failure outranks Monitoring
- Reset merge confirmation when PR or monitor status changes
- endThreadMonitor honors a caller-provided endedAt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotgg force-pushed the t3code/add-pr-monitoring-flow branch from 6d65afe to 0c1de35 Compare July 27, 2026 02:42
Comment thread apps/server/src/monitor/PullRequestMonitor.ts Outdated
Comment thread apps/server/src/monitor/PullRequestMonitor.ts Outdated
Comment thread apps/server/src/monitor/PullRequestMonitor.ts
Comment thread apps/server/src/monitor/MonitorRegistry.ts Outdated
- Serialize wake claim transitions with a semaphore so the post-dispatch
  cursor ack and a concurrent release can no longer interleave; the ack
  and wake-count increment are now one critical section with the claim
  check (Macroscope)
- Rewind the cursor on session-set(error): the provider reactor emits it
  before provider.turn.start.failed, so dropping the claim there left the
  failure handler nothing to restore and acked events were lost. A clean
  session finish still just frees the claim (Bugbot, high)
- Only mark boot reconciliation complete when every orphan ended
  successfully, so failures are retried on the next poll (Bugbot)
- Drop the registration when session teardown runs before the engine is
  bound, instead of leaving a monitor that can wake a dead session
  (Bugbot)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect Service Conventions: 2 findings. Two standalone *Shape service interfaces were introduced; the conventions require the service interface to be declared inline in the Context.Service tag and referenced as Service["Service"].

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/monitor/MonitorRegistry.ts
readonly previousCursor: PullRequestMonitorCursor;
}

export interface PullRequestSnapshotFetcherShape {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standalone PullRequestSnapshotFetcherShape interface. Same convention as MonitorRegistryShape: declare the interface inline in the Context.Service tag and reference it as PullRequestSnapshotFetcher["Service"] rather than keeping a separate FooShape type.

export class PullRequestSnapshotFetcher extends Context.Service<PullRequestSnapshotFetcher>()(
  "t3/monitor/PullRequestMonitor/PullRequestSnapshotFetcher",
  {
    fetch: (input: { readonly cwd: string; readonly pullRequestNumber: number }) =>
      Effect.Effect<PullRequestMonitorSnapshot, GitHubPullRequestMonitorError>,
  },
) {}

Posted via Macroscope — Effect Service Conventions

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the sibling comment: FooShape + Context.Service<Foo, FooShape> is the prevailing pattern in apps/server (43 occurrences, zero using the inline-members overload), so PullRequestSnapshotFetcherShape stays consistent with its neighbors. A codebase-wide migration would be a separate PR.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 355a289. Configure here.

Comment thread apps/server/src/monitor/PullRequestMonitor.ts
The domain-event fiber can observe session-set(error) after the
turn.start command is accepted but before dispatchWake stamps the claim
in-flight. Phase-guarding the release left that pending claim behind
with nothing else to clear it, blocking every future wake for the
thread. Release regardless of phase — releaseClaim only rewinds
in-flight claims, so a pending release is just a drop.

Regression test verified to fail against the phase-guarded release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant